﻿// //   Copyright 2007-2011 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// //   Supported by Media Technology LTD 
// //    
// //   Licensed under the Apache License, Version 2.0 (the "License");
// //   you may not use this file except in compliance with the License.
// //   You may obtain a copy of the License at
// //    
// //        http://www.apache.org/licenses/LICENSE-2.0
// //    
// //   Unless required by applicable law or agreed to in writing, software
// //   distributed under the License is distributed on an "AS IS" BASIS,
// //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //   See the License for the specific language governing permissions and
// //   limitations under the License.
// //   
// //   MODIFICATIONS HAVE BEEN MADE TO THIS FILE

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Comdiv.QWeb.Serialization.Attributes;
using Comdiv.QWeb.Utils;

namespace Comdiv.QWeb.Serialization.Internals {
	public class SerializableItem {
		private readonly object target;
		private readonly bool valueprepared;
		private object _value;

		public SerializableItem(string name, object value, Type type) {
			Value = value;
			Type = type ?? (null == value ? typeof (object) : value.GetType());
			Name = string.IsNullOrWhiteSpace(name) ? Type.Name : name;
			IsFinal = (type.IsValueType || type == typeof (string));
		}

		public SerializableItem(FieldInfo field, object target)
			: this(field.Name, field.GetValue(target), field.FieldType) {
			Member = field;
			valueprepared = true;
			var sa = Member.DeclaringType.getFirstAttribute<SerializeAttribute>();
			if (null != sa && sa.camelNames) {
				Name = Name.Substring(0, 1).ToLower() + Name.Substring(1);
			}
		}

		public SerializableItem(PropertyInfo property, object target)
			: this(property.Name, null, property.PropertyType) {
			Member = property;
			this.target = target;
			valueprepared = false;
			var sa = Member.DeclaringType.getFirstAttribute<SerializeAttribute>();
			if (null != sa && sa.camelNames) {
				Name = Name.Substring(0, 1).ToLower() + Name.Substring(1);
			}
		}

		public MemberInfo Member { get; set; }

		public bool IsSerializable {
			get {
				if (Member.Name == "__interceptors") return false; //ioc includes
				if (Member.DeclaringType.GetCustomAttributes(typeof (CompilerGeneratedAttribute), true)
				    	.Count() > 0) return true; //anonymous class property|field
				if (checkIgnore()) {
					if (checkType()) {
						if (checkNull()) {
							return true;
						}
					}
				}
				return false;
			}
		}

		public object Value {
			get {
				if (!valueprepared) {
					_value = ((PropertyInfo) Member).GetValue(target, null);
				}
				return _value;
			}
			private set { _value = value; }
		}

		public bool IsFinal { get; private set; }
		public string Name { get; private set; }
		public Type Type { get; private set; }

		public static IEnumerable<SerializableItem> GetSerializableItems(object target) {
			var result = new List<SerializableItem>();
			foreach (var field in target.GetType().GetFields()) {
				result.Add(new SerializableItem(field, target));
			}
			foreach (var field in target.GetType().GetProperties()) {
				try {
					result.Add(new SerializableItem(field, target));
				}
				catch (TargetInvocationException) {
				}
				catch (TargetParameterCountException) {
				}
			}
			return result.Where(x => x.IsSerializable);
		}

		private bool checkNull() {
			if (null == Member) return true;
			if (!isattrsetted<NotnullAttribute>(Member)) return true;
			if (IsFinal) {
				if (typeof (string) == Type) return !string.IsNullOrWhiteSpace((string) Value);
				if (typeof (DateTime) == Type) return DateTime.MinValue != (DateTime) Value;
				if (Type.IsEnum) return true;
				if (Type.IsArray) return 0 != ((Array) Value).Length;
				return !Equals(Value, Activator.CreateInstance(Type));
			}
			else {
				return null != Value;
			}
		}

		private bool isattrsetted<T>(Type type) where T : Attribute {
			if (0 != type.GetCustomAttributes(typeof (T), true).Length) return true;
			foreach (var i in type.GetInterfaces()) {
				if (0 != i.GetCustomAttributes(typeof (T), true).Length) return true;
			}
			return false;
		}

		private bool isattrsetted<T>(MemberInfo member) where T : Attribute {
			if (0 != member.GetCustomAttributes(typeof (T), true).Length) return true;
			foreach (var i in member.DeclaringType.GetInterfaces()) {
				var im = i.GetMember(member.Name).FirstOrDefault(x => x.GetType() == member.GetType());
				if (null != im && 0 != im.GetCustomAttributes(typeof (T), true).Length)
					return true;
			}
			return false;
		}

		private bool checkType() {
			try {
				if (Member is PropertyInfo) {
					//no support for indexers
					var idx = ((PropertyInfo) Member).GetIndexParameters();
					if (null != idx && 0 != idx.Length) {
						return false;
					}
				}
				if (IsFinal) return true;
				if (isattrsetted<SerializeAttribute>(Member)) return true;
				if (typeof (IDictionary).IsAssignableFrom(Type) ||
				    (Value != null && typeof (IDictionary).IsAssignableFrom(Value.GetType())))
					return true;
				if (typeof (Array).IsAssignableFrom(Type) ||
				    (Value != null && typeof (Array).IsAssignableFrom(Value.GetType())))
					return true;
				if (Value != null && Value.GetType().Name.StartsWith("<")) return true;
				return isattrsetted<SerializeAttribute>(Type);
			}
			catch (Exception) {
				return false;
			}
		}

		private bool checkIgnore() {
			if (null == Member) return true;
			return !isattrsetted<IgnoreSerializeAttribute>(Member);
		}
	}
}